package com.zx.shiro.realms;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.LogoutAware;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;

import com.zx.shiro.session.MySessionDao;
import com.zx.shiro.utils.SerializableUtils;

public class ShiroRealm extends AuthorizingRealm {
	
	@Autowired
	private static MySessionDao sessionDAO;

	/**
	 * 认证（登录）
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[FirstRealm] doGetAuthenticationInfo ");
		
		//1.把AuthenticationToken转换为UsernamePasswordToken
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		
		//2.从UsernamePasswordToken中获取username
		String username = upToken.getUsername();
		
		//3.调用数据库的方法，从数据库中查询username对应的用户记录
		System.out.println("从数据库中获取username：" + username + " 所对应的用户信息。");
		
		//4.若用户不存在，则可以抛出UnknownAccountException异常
		if("unkonw".equals(username)){
			throw new UnknownAccountException();
		}
		
		//5.根据用户信息的情况，决定是否需要抛出其他的AuthenticationException异常
		if("monster".equals(username)){
			throw new LockedAccountException();
		}
		
		//6.根据用户的情况，来构建AuthenticationInfo对象并返回，通常使用的实现类为：。
		//以下信息是从数据库中获取的
		//1).principal:认证的实体信息，可以是username，也可以是数据表对应的用户的实体类对象
		Object principal = username;
		//2).credentials:密码
		Object credentials = "";
		if("admin".equals(username)){
			credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
		}
		if("user".equals(username)){
			credentials = "098d2c478e9c11555ce2823231e02ec1";
		}
		
		//3).realmName:当前realm对象的name，调用父类的getName()方法即可
		String realmName = getName();
		//4).盐值（因为用户名的值唯一，所以用用户名的值作为盐值，防止用户密码相同）
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		
		SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
		info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		System.out.println("getSession().getAttributeKeys() == "+SecurityUtils.getSubject().getSession().getAttributeKeys());
		//验证成功开始踢人(清除缓存和Session)======此处是为了实现单点登录，(一个用户同一时刻只能在一个地方登录)
		//deleteCache(username, true);
		////===========================
		return info;
	}

	/**
	 * 字符串加密方法
	 * @param args
	 */
	public static void main(String[] args){
		//加密名称
		String hashAlgorithmName = "MD5";
		//需要加密的密码
		Object credentials = "123456";
		//加盐
		Object salt = ByteSource.Util.bytes("admin");
		//加密次数
		int hashIterations = 1024;
		//加密方法
		Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
		//打印输出加密后的结果
		System.out.println(result);
	
	}

	/**
	 * 授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		System.out.println("[FirstRealm] doGetAuthorizationInfo ");
		//1.从PrincipalCollection中获取登录用户的信息
		Object principal = principals.getPrimaryPrincipal();
		
		//2.利用登录的用户信息来获取当前用户的角色或权限（可能需要查询数据库）
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)){
			roles.add("admin");
		}
		
		//3.创建SimpleAuthorizationInfo，并设置其roles属性
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
				
		//4.返回SimpleAuthorizationInfo对象
		return info;
		
	}
	
	/**
     * 删除用户缓存信息
     * @Author Sans
     * @CreateTime 2019/6/17 13:57
     * @Param  username  用户名称
     * @Param  isRemoveSession 是否删除Session
     * @Return void
     */
    public static void deleteCache(String username, boolean isRemoveSession){
        //从缓存中获取Session
        Session session = null;
        List<Map<String, Object>> sessions = MySessionDao.sessionList;
        String principal;
        Object attribute = null;
        for(Map<String, Object> map : sessions){
        	Session sessionInfo = SerializableUtils.deserialize((String)map.get("session")); 
        	System.out.println("DefaultSubjectContext.PRINCIPALS_SESSION_KEY === " + DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            //遍历Session,找到该用户名称对应的Session
            attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {
                continue;
            }
            principal = (String) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (principal == null) {
                continue;
            }
            if (Objects.equals(principal, username)) {
                session=sessionInfo;
                break;
            }
        }
        if (session == null||attribute == null) {
            return;
        }
        //删除session
        if (isRemoveSession) {
        	System.out.println("删除session");
        	sessionDAO.delete(session);
        }
        //删除Cache，在访问受限接口时会重新授权
        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
        Authenticator authc = securityManager.getAuthenticator();
        ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
        System.out.println("删除删除Cache");
    }
	
}
